package org.dfl.plugin.editor;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.EventObject;
import java.util.Iterator;
import java.util.List;

import org.apache.log4j.Logger;
import org.dfl.core.utils.RNGUtils;
import org.dfl.messages.Messages;
import org.dfl.plugin.DflPlugin;
import org.dfl.plugin.dialogs.DialogsManager;
import org.dfl.plugin.editor.actions.AlignHorizontallyAction;
import org.dfl.plugin.editor.actions.AlignVerticallyAction;
import org.dfl.plugin.editor.actions.CheckHierarchicalAction;
import org.dfl.plugin.editor.actions.ClearFlowStateAction;
import org.dfl.plugin.editor.actions.ConnectionEditAction;
import org.dfl.plugin.editor.actions.LoadAllTokensAction;
import org.dfl.plugin.editor.actions.PauseAction;
import org.dfl.plugin.editor.actions.ResumeAction;
import org.dfl.plugin.editor.actions.ResumeOnceStepAction;
import org.dfl.plugin.editor.actions.ResumeOnceStepDisabledTokensAction;
import org.dfl.plugin.editor.actions.RunAction;
import org.dfl.plugin.editor.actions.SaveAllTokensAction;
import org.dfl.plugin.editor.actions.StopAction;
import org.dfl.plugin.editor.actions.TavernaTransitionEditAction;
import org.dfl.plugin.editor.actions.TransitionEditAction;
import org.dfl.plugin.editor.markers.GlobalMarkersManager;
import org.dfl.plugin.editor.model.DflDiagram;
import org.dfl.plugin.editor.parts.DflEditPartFactory;
import org.dfl.plugin.views.ViewsManager;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.draw2d.LayeredPane;
import org.eclipse.gef.GraphicalViewer;
import org.eclipse.gef.LayerConstants;
import org.eclipse.gef.dnd.TemplateTransferDropTargetListener;
import org.eclipse.gef.editparts.ScalableFreeformRootEditPart;
import org.eclipse.gef.editparts.ZoomManager;
import org.eclipse.gef.requests.CreationFactory;
import org.eclipse.gef.requests.SimpleFactory;
import org.eclipse.gef.ui.actions.PrintAction;
import org.eclipse.gef.ui.actions.SaveAction;
import org.eclipse.gef.ui.actions.UpdateAction;
import org.eclipse.gef.ui.actions.ZoomInAction;
import org.eclipse.gef.ui.actions.ZoomOutAction;
import org.eclipse.gef.ui.parts.GraphicalEditor;
import org.eclipse.gef.ui.parts.GraphicalViewerKeyHandler;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.dialogs.ProgressMonitorDialog;
import org.eclipse.jface.util.TransferDropTargetListener;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.actions.WorkspaceModifyOperation;
import org.eclipse.ui.dialogs.SaveAsDialog;
import org.eclipse.ui.part.FileEditorInput;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.input.SAXBuilder;
import org.jdom.output.Format;
import org.jdom.output.XMLOutputter;


/**
 * A graphical editor with dfl palette that can edit .dfl files. The
 * binding between the .dfl file extension and this editor is done in
 * plugin.xml
 * 
 * @author Piotr Wlodarczyk
 */
public class DflEditor extends GraphicalEditor {
	private static Logger logger = Logger.getLogger(DflEditor.class);
	
	/** This is the root of the editor's model. */
	private DflDiagram diagram;
	private GraphicalViewer viewer;

	/** Create a new DflEditor instance. This is called by the Workspace. */
	public DflEditor() {
		setEditDomain(new DflEditDomain(this));
	}

	/**
	 * Configure the graphical viewer before it receives contents.
	 * <p>
	 * This is the place to choose an appropriate RootEditPart and
	 * EditPartFactory for your editor. The RootEditPart determines the behavior
	 * of the editor's "work-area". For example, GEF includes zoomable and
	 * scrollable root edit parts. The EditPartFactory maps model elements to
	 * edit parts (controllers).
	 * </p>
	 * 
	 * @see org.eclipse.gef.ui.parts.GraphicalEditor#configureGraphicalViewer()
	 */
	@SuppressWarnings({ "deprecation", "unchecked" }) //$NON-NLS-1$ //$NON-NLS-2$
	protected void configureGraphicalViewer() {
		super.configureGraphicalViewer();

		viewer = getGraphicalViewer();
		ScalableFreeformRootEditPart root = new ScalableFreeformRootEditPart() {
			protected void createLayers(LayeredPane layeredPane) {
				super.createLayers(layeredPane);
				// enabled because needed by EntitySelectionEditPolicy
				getLayer(LayerConstants.SCALED_FEEDBACK_LAYER).setEnabled(true);
			}
		};

		// register Actions
		List<String> zoomLevels = new ArrayList<String>(3);
		zoomLevels.add(ZoomManager.FIT_ALL);
		zoomLevels.add(ZoomManager.FIT_WIDTH);
		zoomLevels.add(ZoomManager.FIT_HEIGHT);
		root.getZoomManager().setZoomLevelContributions(zoomLevels);

		IAction zoomIn = new ZoomInAction(root.getZoomManager());
		IAction zoomOut = new ZoomOutAction(root.getZoomManager());
		getActionRegistry().registerAction(zoomIn);
		getActionRegistry().registerAction(zoomOut);
		getSite().getKeyBindingService().registerAction(zoomIn);
		getSite().getKeyBindingService().registerAction(zoomOut);
		
	    addEditorAction(new SaveAction(this));
	    addEditorAction(new PrintAction(this));

		// add edit actions
		addEditorAction(new TavernaTransitionEditAction(this));
		addEditorAction(new TransitionEditAction(this));
		addEditorAction(new ConnectionEditAction(this));
		addEditorAction(new AlignVerticallyAction(this,diagram));
		addEditorAction(new AlignHorizontallyAction(this,diagram));
		addEditorAction(new RunAction(this,diagram));
		addEditorAction(new CheckHierarchicalAction(this,diagram));
		addEditorAction(new StopAction(this,diagram));
		addEditorAction(new PauseAction(this,diagram));
		addEditorAction(new ResumeAction(this,diagram));
		addEditorAction(new ResumeOnceStepAction(this,diagram));
		addEditorAction(new ResumeOnceStepDisabledTokensAction(this,diagram));
		addEditorAction(new SaveAllTokensAction(this,diagram));
		addEditorAction(new LoadAllTokensAction(this,diagram));
		addEditorAction(new ClearFlowStateAction(this,diagram));

		viewer.setRootEditPart(root);
		// viewer.setRootEditPart(new ScalableFreeformRootEditPart());
		viewer.setEditPartFactory(new DflEditPartFactory());

		viewer.setKeyHandler(new GraphicalViewerKeyHandler(viewer));

		// configure the context menu provider
		final DflEditorContextMenuProvider cmProvider = new DflEditorContextMenuProvider(viewer, getActionRegistry(),diagram);
		viewer.setContextMenu(cmProvider);
		getSite().registerContextMenu(cmProvider, viewer);
		
		//add listeners
		ViewsManager.registerEditor(this);
	}
	
    /**
     * Adds an editor action to this editor.
     * 
     * <p><Editor actions are actions that depend
     * and work on the editor.
     * 
     * @param action the editor action
     */
    @SuppressWarnings("unchecked")
	protected void addEditorAction(IAction action)
    {
        getActionRegistry().registerAction(action);
        getSelectionActions().add(action.getId());
    }

  // use this function to refresh all actions in the context menu and so on	
	@SuppressWarnings("unchecked")
	public void updateActions() {
		Iterator iter = getActionRegistry().getActions();
		while(iter.hasNext()){
			IAction action = (IAction) iter.next();
			if (null != action && action instanceof UpdateAction)
                ((UpdateAction) action).update();
		}
	}
	
	public GraphicalViewer getViewer() {
		return viewer;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.gef.ui.parts.GraphicalEditor#commandStackChanged(java.util.EventObject)
	 */
	public void commandStackChanged(EventObject event) {
		firePropertyChange(IEditorPart.PROP_DIRTY);
		super.commandStackChanged(event);
	}

	private void createOutputStream(OutputStream os) throws IOException {
		new XMLOutputter(Format.getPrettyFormat()).output(new Document(getModel().save()), os);
	}

	/**
	 * Create a transfer drop target listener. When using a
	 * CombinedTemplateCreationEntry tool in the palette, this will enable model
	 * element creation by dragging from the palette.
	 * 
	 * @see #createPaletteViewerProvider()
	 */
	private TransferDropTargetListener createTransferDropTargetListener() {
		return new TemplateTransferDropTargetListener(getGraphicalViewer()) {
			@SuppressWarnings("unchecked")
			protected CreationFactory getFactory(Object template) {
				CreationFactory ret = super.getFactory(template);
				if (ret == null) {
					return new SimpleFactory((Class) template);
				} else {
					return ret;
				}
			}
		};
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.ui.ISaveablePart#doSave(org.eclipse.core.runtime.IProgressMonitor)
	 */
	public void doSave(IProgressMonitor monitor) {
		ByteArrayOutputStream out = new ByteArrayOutputStream();
		try {
			createOutputStream(out);
			IFile file = ((IFileEditorInput) getEditorInput()).getFile();
			file.setContents(new ByteArrayInputStream(out.toByteArray()), true, // keep
					// saving,
					// even
					// if
					// IFile
					// is
					// out
					// of
					// sync
					// with
					// the
					// Workspace
					false, // dont keep history
					monitor); // progress monitor
			getCommandStack().markSaveLocation();
		} catch (Exception ce) {
			DialogsManager.openErrorDialog(ce);
		} 
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.ui.ISaveablePart#doSaveAs()
	 */
	public void doSaveAs() {
		// Show a SaveAs dialog
		Shell shell = getSite().getWorkbenchWindow().getShell();
		SaveAsDialog dialog = new SaveAsDialog(shell);
		dialog.setOriginalFile(((IFileEditorInput) getEditorInput()).getFile());
		dialog.open();

		IPath path = dialog.getResult();
		if (path != null) {
			// try to save the editor's contents under a different file name
			final IFile file = ResourcesPlugin.getWorkspace().getRoot().getFile(path);
			try {
				new ProgressMonitorDialog(shell).run(false, // don't fork
						false, // not cancelable
						new WorkspaceModifyOperation() { // run this
							// operation
							public void execute(final IProgressMonitor monitor) {
								try {
									ByteArrayOutputStream out = new ByteArrayOutputStream();
									createOutputStream(out);
									file.create(new ByteArrayInputStream(out.toByteArray()), // contents
											true, // keep saving, even if
											// IFile is out of sync with
											// the Workspace
											monitor); // progress monitor
								} catch (Exception ce) {
									DialogsManager.openErrorDialog(ce);
								} 
							}
						});
				// set input to the new file
				setInput(new FileEditorInput(file));
				getCommandStack().markSaveLocation();
			} catch (Exception ce) {
				DialogsManager.openErrorDialog(ce);
			} 
		}
	}

	@SuppressWarnings("unchecked")
	public Object getAdapter(Class type) {
		if (type == ZoomManager.class)
			return getGraphicalViewer().getProperty(ZoomManager.class.toString());
		return super.getAdapter(type);
	}

	DflDiagram getModel() {
		return diagram;
	}

	private void handleLoadException(Throwable e) {
		logger.error(e.getMessage());
	}

	/**
	 * Set up the editor's inital content (after creation).
	 * 
	 * @see org.eclipse.gef.ui.parts.GraphicalEditorWithFlyoutPalette#initializeGraphicalViewer()
	 */
	protected void initializeGraphicalViewer() {
		GraphicalViewer viewer = getGraphicalViewer();
		viewer.setContents(getModel()); // set the contents of this editor

		// listen for dropped parts
		viewer.addDropTargetListener(createTransferDropTargetListener());
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.ui.ISaveablePart#isSaveAsAllowed()
	 */
	public boolean isSaveAsAllowed() {
		return true;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.ui.part.EditorPart#setInput(org.eclipse.ui.IEditorInput)
	 */
	protected void setInput(IEditorInput input) {
		super.setInput(input);
		try {
			IFile file = ((IFileEditorInput) input).getFile();
			setPartName(file.getName());

			// full file path (for validation)
			String path = file.getLocation().toFile().getAbsolutePath();

			// sprawdzanie poprawnosci wejscia
			RNGUtils.checkRNGFileIntegrity(path, DflPlugin.getURLFromPlugin(Messages.DflEditor_rngFile));

			SAXBuilder builder = new SAXBuilder(false);
			Document document = builder.build(file.getContents());
			Element root = document.getRootElement();

			// if loading from saved data file - the root it <dfl> otherwise the
			// document root
			GlobalMarkersManager.unregisterResource(file);
			diagram = new DflDiagram(file, root.getChild("dfl") != null ? root.getChild("dfl") : root, this); //$NON-NLS-1$ //$NON-NLS-2$
		} catch (Throwable e) {
			handleLoadException(e);
		}
	}

	@Override
	public void dispose() {
		diagram.stop();
		super.dispose();
		// TODO comment this if after closing diagram markers should be not
		// unregistered
		// GlobalMarkersManager.unregisterResource(diagram.getDiagramResource());
	}
	
	
	public void resetTool() {
		getEditDomain().setActiveTool(null);
	}
}
